package com.dev.security.config;

import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import com.dev.security.manager.dao.AccountRepository;
import com.mchange.v2.c3p0.ComboPooledDataSource;

@Configuration
@EnableJpaRepositories("com.dev.**.dao")
@ComponentScan(basePackages = {"com.dev.**.dao", "com.dev.**.service"})
@EnableTransactionManagement
public class JPAConfig {

	@Autowired
	private Environment env;
	
	@Bean
	public DataSource dataSource() throws PropertyVetoException {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setDriverClass(env.getProperty("db.driver.class"));
		dataSource.setJdbcUrl(env.getProperty("db.url"));
		dataSource.setUser(env.getProperty("db.username"));
		dataSource.setPassword(env.getProperty("db.password"));
		
		dataSource.setAutoCommitOnClose(env.getProperty("db.autoCommitOnClose", Boolean.class));
		dataSource.setMinPoolSize(env.getProperty("db.minPoolSize", Integer.class));
		dataSource.setMaxPoolSize(env.getProperty("db.maxPoolSize", Integer.class));
		dataSource.setInitialPoolSize(env.getProperty("db.initialPoolSize", Integer.class));
		dataSource.setMaxIdleTime(env.getProperty("db.maxIdleTime", Integer.class));
		dataSource.setAcquireIncrement(env.getProperty("db.acquireIncrement", Integer.class));
		dataSource.setAcquireRetryAttempts(env.getProperty("db.acquireRetryAttempts", Integer.class));
		dataSource.setCheckoutTimeout(env.getProperty("db.checkoutTimeout", Integer.class));
		return dataSource;
	}
	
	@Bean
	public EntityManagerFactory entityManagerFactory() throws PropertyVetoException {
//		HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
//		vendorAdapter.setGenerateDdl(true);
//		vendorAdapter.setShowSql(true);
		Properties jpaProperties = new Properties();
		jpaProperties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
		jpaProperties.put("hibernate.max_fetch_depth", env.getProperty("hibernate.max_fetch_depth"));
		jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
		jpaProperties.put("hibernate.jdbc.fetch_size", env.getProperty("hibernate.jdbc.fetch_size"));
		jpaProperties.put("hibernate.jdbc.batch_size", env.getProperty("hibernate.jdbc.batch_size"));
		jpaProperties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
		jpaProperties.put("hibernate.format_sql", env.getProperty("hibernate.format_sql"));
		jpaProperties.put("hibernate.generate_statistics", env.getProperty("hibernate.generate_statistics"));
		
		LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
//		factory.setJpaVendorAdapter(vendorAdapter);
		factory.setJpaDialect(new HibernateJpaDialect());
		factory.setPersistenceProviderClass(HibernatePersistenceProvider.class);
		factory.setPackagesToScan("com.dev.**.beans");
		factory.setJpaProperties(jpaProperties);
		factory.setDataSource(dataSource());
		factory.setLoadTimeWeaver(instrumentationLoadTimeWeaver());
		factory.afterPropertiesSet();
		return factory.getObject();
	}

	@Bean
	public PlatformTransactionManager transactionManager() throws PropertyVetoException {
		JpaTransactionManager txManager = new JpaTransactionManager();
		txManager.setEntityManagerFactory(entityManagerFactory());
		return txManager;
	}

	@Bean
	public InstrumentationLoadTimeWeaver instrumentationLoadTimeWeaver() {
		return new InstrumentationLoadTimeWeaver();
	}

	@Bean
	public TransactionInterceptor transactionInterceptor() throws PropertyVetoException {
		TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
		transactionInterceptor.setTransactionManager(transactionManager());
		NameMatchTransactionAttributeSource transactionAttributeSource = new NameMatchTransactionAttributeSource();
		RuleBasedTransactionAttribute attr = new RuleBasedTransactionAttribute();
		List<RollbackRuleAttribute> rollbackRules = new ArrayList<RollbackRuleAttribute>();
		RollbackRuleAttribute rule = new RollbackRuleAttribute(Exception.class);
		rollbackRules.add(rule);
		attr.setRollbackRules(rollbackRules);
		transactionAttributeSource.addTransactionalMethod("*", attr);
		transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource);
		//transactionInterceptor.setTransactionAttributeSource(new AnnotationTransactionAttributeSource());
		return transactionInterceptor;
	}
	
//	@Bean
//	public DefaultPointcutAdvisor daoPointcutAdvisor() throws PropertyVetoException {
//		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
//		advisor.setAdvice(transactionInterceptor());
//		NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
//		pointcut.setMappedName("execution(* *..dao.**.*(..))");
//		advisor.setPointcut(pointcut);
//		advisor.setOrder(1);
//		return advisor;
//	}
	
	@Bean
	public DefaultPointcutAdvisor servicePointcutAdvisor() throws PropertyVetoException {
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setAdvice(transactionInterceptor());
		AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
		pointcut.setExpression("execution(* *..service.*Service.*(..))");
		advisor.setPointcut(pointcut);
		advisor.setOrder(1);
		return advisor;
	}
	
	@Bean
	public DefaultPointcutAdvisor controllerPointcutAdvisor() throws PropertyVetoException {
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setAdvice(transactionInterceptor());
		AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
		pointcut.setExpression("execution(* *..controllers.**.*(..))");
		advisor.setPointcut(pointcut);
		advisor.setOrder(2);
		return advisor;
	}
	
	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		return new DefaultAdvisorAutoProxyCreator();
	}
	
	@Autowired
	protected AccountRepository accountRepository;
}
